package com.reyco1.medusa.display.graphics
{
	import com.reyco1.medusa.display.SmartSprite;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Mouse;
	import flash.utils.setTimeout;
	
	public class Canvas extends SmartSprite
	{
		private var _penColor:uint;
		private var _penThickness:int;
		private var _backGroundColor:Number;
		private var _canvasWidth:Number;
		private var _canvasHeight:Number;
		private var _undoLevels:int;
		private var _drawEase:Number;
		private var _penClipClass:DisplayObject;
		private var _isActive:Boolean;
		
		private var penClip:MovieClip;
		private var undoStack:Vector.<BitmapData>;
		private var canvasBitmapData:BitmapData;
		private var canvasBitmap:Bitmap;
		private var bitmapHolder:Sprite;
		private var lineLayer:Sprite;
		private var background:Sprite;
		private var mouseMove:Boolean;
		private var lastPoint:Point;		
		
		public function Canvas(canvasWidth:Number = 800, canvasHeight:Number = 600)
		{
			super();
			
			this.canvasWidth  = canvasWidth;
			this.canvasHeight = canvasHeight;
			
			_penColor 	 	 = 0x000000;
			_penThickness 	 = 2;
			_backGroundColor = -1;
			_undoLevels 	 = 10;
			_drawEase		 = 0.5;
			
			lineLayer 	 = new Sprite();		
			background 	 = new Sprite();
			bitmapHolder = new Sprite();
			
			mouseChildren = mouseEnabled = false;
			
			addChild(background);
			addChild(bitmapHolder);
		}
		
		private function invalidate():void
		{
			var color:Number   = backGroundColor == -1 ? 0x000000 : backGroundColor;
			var opacity:Number = backGroundColor == -1 ? 0 : 1;
			
			with(background.graphics)
			{
				clear();
				beginFill(color, opacity);
				drawRect(0, 0, canvasWidth, canvasHeight);
				endFill();
			}
			
			clear();
		}
		
		private function isMouseWithinCanvas(mousePoint:Point):Boolean
		{
			var isWithin:Boolean = false;
			var globalCanvasPoint:Point = localToGlobal(new Point(background.x, background.y));
			var bounds:Rectangle = new Rectangle(globalCanvasPoint.x, globalCanvasPoint.y, canvasWidth, canvasHeight);
			
			if(bounds.contains(mousePoint.x, mousePoint.y))
			{
				isWithin = true;
			}
			
			return isWithin;
		}
		
		private function updateMouseCursor():void
		{
			try
			{
				Mouse.hide();			
				penClip = _penClipClass as MovieClip;
				penClip.mouseEnabled = false;
				stage.addChild(penClip);
				stage.addEventListener(MouseEvent.MOUSE_MOVE, updateCursor);
			}
			catch(e:Error)
			{
				setTimeout(updateMouseCursor, 100);
			}
		}
		
		override protected function onAdded():void
		{
			invalidate();
		}
		
		override protected function onRemoved():void
		{
			deactivate();
			if(penClip)
			{
				stage.removeChild(penClip);
				penClip = null;
				Mouse.show();
			}
		}
		
		public function activate():void
		{
			stage.addEventListener(MouseEvent.MOUSE_DOWN, startDraw);
			_isActive = true;
		}
		
		public function deactivate():void
		{
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, startDraw);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, updateCursor);
			_isActive = false;
		}
		
		public function undo():void 
		{
			if (undoStack.length > 1) 
			{
				canvasBitmapData.copyPixels(undoStack[undoStack.length - 2], canvasBitmapData.rect, new Point(0,0));
				undoStack.splice(undoStack.length - 1, 1);
			}
		}
		
		override public function clear():void
		{
			undoStack = new Vector.<BitmapData>;	
			
			canvasBitmapData = new BitmapData(canvasWidth, canvasHeight, true, 0x00000000);
			canvasBitmap 	 = new Bitmap(canvasBitmapData, "auto", true);			
			
			while(bitmapHolder.numChildren > 0) bitmapHolder.removeChildAt(0);
			bitmapHolder.addChild(canvasBitmap);
			
			var undoBuffer:BitmapData = new BitmapData(canvasWidth, canvasHeight, true, 0x00000000);
			undoBuffer.copyPixels(canvasBitmapData, undoBuffer.rect, new Point(0,0));
			undoStack.push(undoBuffer);
		}
		
		/* handlers */
		
		private function startDraw(e:MouseEvent):void
		{
			var globalMousePoint:Point = new Point(stage.mouseX, stage.mouseY);
			var localMousePoint:Point = globalToLocal(globalMousePoint);
			
			if( isMouseWithinCanvas(globalMousePoint) )
			{
				mouseMove = false;	
				lastPoint = localMousePoint.clone();
				
				with(lineLayer.graphics)
				{
					clear();
					lineStyle(penThickness, penColor, 1, true);
					moveTo(localMousePoint.x, localMousePoint.y);
				}			
				
				stage.addEventListener(Event.ENTER_FRAME, drawLine);
				stage.addEventListener(MouseEvent.MOUSE_MOVE, setMouseMoving);
				stage.addEventListener(MouseEvent.MOUSE_UP, updateStack);
			}			
		}
		
		private function setMouseMoving(e:MouseEvent):void
		{
			mouseMove = true;
		}
		
		private function drawLine(e:Event):void
		{			
			var currentPoint:Point = globalToLocal(new Point(stage.mouseX, stage.mouseY));
			currentPoint = new Point(lastPoint.x + (currentPoint.x - lastPoint.x) * drawEase, 
				lastPoint.y + (currentPoint.y - lastPoint.y) * drawEase);
			
			lineLayer.graphics.lineTo(currentPoint.x, currentPoint.y);
			canvasBitmapData.draw(lineLayer, null, null, null, null, true);
			
			lastPoint = currentPoint;
		}
		
		private function updateStack(e:MouseEvent):void
		{
			stage.removeEventListener(Event.ENTER_FRAME, drawLine);
			stage.removeEventListener(MouseEvent.MOUSE_UP, updateStack);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, setMouseMoving);
			
			if(mouseMove)
			{			
				var undoBuffer:BitmapData = new BitmapData(canvasWidth, canvasHeight, true, 0xFFFFFFFF);
				undoBuffer.copyPixels(canvasBitmapData, undoBuffer.rect, new Point(0,0));
				undoStack.push(undoBuffer);
				
				if (undoStack.length > undoLevels + 1) undoStack.splice(0, 1);
			}
			
			mouseMove = false;
			
			e.updateAfterEvent();
		}
		
		private function updateCursor(e:MouseEvent):void
		{
			_penClipClass.x = stage.mouseX;
			_penClipClass.y = stage.mouseY;
			e.updateAfterEvent();
		}
		
		/* properties */
		
		public function get penColor():uint
		{
			return _penColor;
		}
		
		public function set penColor(value:uint):void
		{
			_penColor = value;
		}
		
		public function get penThickness():int
		{
			return _penThickness;
		}
		
		public function set penThickness(value:int):void
		{
			_penThickness = value;
		}
		
		public function get backGroundColor():Number
		{
			return _backGroundColor;
		}
		
		public function set backGroundColor(value:Number):void
		{
			_backGroundColor = value;
			invalidate();
		}
		
		public function get undoLevels():int
		{
			return _undoLevels;
		}
		
		public function set undoLevels(value:int):void
		{
			_undoLevels = value;
		}
		
		public function get canvasWidth():Number
		{
			return _canvasWidth;
		}
		
		public function set canvasWidth(value:Number):void
		{
			_canvasWidth = value;
		}
		
		public function get canvasHeight():Number
		{
			return _canvasHeight;
		}
		
		public function set canvasHeight(value:Number):void
		{
			_canvasHeight = value;
		}
		
		public function get drawEase():Number
		{
			return _drawEase;
		}
		
		public function set drawEase(value:Number):void
		{
			_drawEase = value;
		}
		
		public function get penClipClass():DisplayObject
		{
			return _penClipClass;
		}
		
		public function set penClipClass(value:DisplayObject):void
		{
			_penClipClass = value;			
			updateMouseCursor();
		}
		
		public function get isActive():Boolean
		{
			return _isActive;
		}
		
		public function set isActive(value:Boolean):void
		{
			_isActive = value;
		}
		
		
	}
}